package net.openvision.music.model;

import java.util.HashMap;
import java.util.Locale;
import java.util.Map;

import net.openvision.music.notenames.DefaultNoteConverter;
import net.openvision.music.notenames.NoteConverter;

public class Note extends BarEntity {

	private static Map<String, NoteConverter> converters;
	private static NoteConverter defaultConverter;
	private static int[] midiPitchMapping;

	static {
		defaultConverter = new DefaultNoteConverter();
		converters = new HashMap<String, NoteConverter>();
		// TODO: implement note (de, us, uk, fr, ...) converters and add here

		midiPitchMapping = new int[] { 0, 2, 4, 5, 7, 9, 11, 12, 14, 16, 17,
				19, 21, 23, 24, 26, 28, 29, 31, 33, 35, 36, 38, 40, 41, 43, 45,
				47, 48, 50, 52, 53, 55, 57, 59, 60, 62, 64, 65, 67, 69, 71, 72,
				74, 76, 77, 79, 81, 83, 84, 86, 88, 89, 91, 93, 95, 96, 98,
				100, 101, 103, 105, 107, 108, 110, 112, 113, 115, 117, 119,
				120, 122, 124, 125, 127 };
	}

	public static Note convert(String string, Locale locale) {
		if (locale == null || !converters.containsKey(locale.getCountry()))
			return defaultConverter.convert(string);
		else
			return converters.get(locale.getCountry()).convert(string);
	}

	private int pitch;
	private int modification;

	private void check() {
		if (length == null)
			throw new NullPointerException("Length may not be null.");
	}

	public Note(int midiPitch, Length length, Clef clef) {
		super(length, clef);
		int oct = midiPitch / 12;
		int rel = midiPitch % 12;
		int i = 0;
		while (midiPitchMapping[i++] < rel)
			;
		pitch = (i - 1) + oct * 7;
		modification = midiPitchMapping[i - 1] - rel;
		check();
	}

	public Note(int pitch, int modification, Length length, Clef clef) {
		super(length, clef);
		this.pitch = pitch;
		this.modification = modification;
		check();
	}

	public Note(String pitch, Length length, Clef clef) {
		this(pitch, length, null, clef);
	}

	public Note(String pitch, Length length, Locale locale, Clef clef) {
		super(length, clef);
		Note n = convert(pitch, locale);
		this.pitch = n.getPitch();
		this.modification = n.getModification();
		check();
	}

	/**
	 * Returns an integer that indicates the pitch of the note that is obtained
	 * by removing the accidentals of the current node. So it only defines the
	 * notes in the c major scale. All other notes in between are characterized
	 * through a {@link getModification} value different to <code>0</code>. A
	 * pitch of <code>40</code> corresponds to A440 (which has a midi value of
	 * 69).
	 * 
	 * @return an integer that indicates the pitch of the note that is obtained
	 *         by removing the accidentals of the current node.
	 */
	public int getPitch() {
		return pitch;
	}

	/**
	 * Returns an integer that indicates if this note is an accidental or in
	 * what way it differs from the corresponding note of the c major scale by
	 * the last recently applied key signature. <code>+1</code> indicates
	 * 'sharp',<code>-1</code> flat, <code>+2</code> double sharp and so on.
	 * 
	 * @return an integer identifying in what matter this note differs from the
	 *         corresponding c major scale note.
	 */
	public int getModification() {
		return modification;
	}

	public int getMidiPitch() {
		return midiPitchMapping[pitch] + modification;
	}
}
